/******************************************************************************

    Simple Authentication and Authorisation System (SAAS)

    net.c

    Copyright (C) 2011  Josh Goes


    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    If you require a mailing address for this software, please send your
    mail to the following address:
        SAAS
        GPO Box 2973
        BRISBANE QLD 4001
        AUSTRALIA

******************************************************************************/


#include "saas-build.h"

#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include <errno.h>



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// EXTERNAL FUNCTIONS
//

uint16_t pkt_transceive( saas_t *inst , uint8_t *pkt , uint16_t *pkt_len , saas_host_t *host )
{
	// determine if the host is IPv4 IP or IPv6 IP or DNS and also extract the port if any
	// if DNS resolve to an IP
	// open socket handle for either AF_INET or AF_INET6
	// send the UDP packet, wait timeout-ms for a response
	// if no resposne return 0 and set pktlen to 0
	// if response pull it down straight into the pkt variable. update pktlen also.

	// TODO: timeout

	struct timeval tm;
	fd_set fds;
	struct sockaddr *sockstr = NULL;
	struct sockaddr_in sock4str;
	struct sockaddr_in6 sock6str;
	socklen_t socklen;
	int sock = 0, ret;

	if ( host->type == SAAS_HOST_TYPE_IPV4 )
	{
		sock = socket( AF_INET , SOCK_DGRAM , 0 );
		sock4str.sin_family = AF_INET;
		memcpy( &sock4str.sin_addr , host->ipv4_addr , 4 );
		sock4str.sin_port = htons( host->port );
		socklen = sizeof( struct sockaddr_in );
		sockstr = (struct sockaddr *) &sock4str;
	}
	else if ( host->type == SAAS_HOST_TYPE_IPV6 )
	{
		sock = socket( AF_INET6 , SOCK_DGRAM , 0 );
		sock6str.sin6_family = AF_INET6;
		memcpy( &sock6str.sin6_addr , host->ipv6_addr , 16 );
		sock6str.sin6_port = htons( host->port );
		socklen = sizeof( struct sockaddr_in6 );
		sockstr = (struct sockaddr *) &sock6str;
	}

	ret = sendto( sock , pkt , *pkt_len , 0 , sockstr , socklen );
	if ( ret != *pkt_len )
	{
		// this typically means there's an error with routing or the system is incapable of the
		// address family, something like that. so we'll report back the error and abort.
		logm( inst , LOG_WARNING , "error transmitting packet (%d bytes): (%d) %s" , *pkt_len , errno , strerror( errno ) );
		return SAAS_OP_SOCKET_ERROR;
	}

	ret = recvfrom( sock , pkt , SAAS_MAX_PKT_LEN , MSG_DONTWAIT , sockstr , &socklen );
	if ( ret == -1 && ( errno == EAGAIN || errno == EWOULDBLOCK ) )
	{
		// we'll have to wait for it.
		tm.tv_sec  = inst->timeout / 1000;
		tm.tv_usec = ( inst->timeout % 1000 ) * 1000;
		FD_ZERO( &fds );
		FD_SET( sock , &fds );
		ret = select( sock + 1 , &fds , NULL , NULL , &tm );
		if ( ret > 0 )
		{
			// there's data ready for us. read it.
			ret = recvfrom( sock , pkt , SAAS_MAX_PKT_LEN , 0 , sockstr , &socklen );
			if ( ret <= 0 )
			{
				// select() told us we were ready, but recvfrom failed. WTF?!?!
				logm( inst , LOG_WARNING , "BUG: OS said data on socket was ready to be read, but read() gave no data?!" );
				close( sock );
				return SAAS_OP_SOCKET_ERROR;
			}
		}
		else
		{
			// a timeout or error happened.
			logm( inst , LOG_INFO , "timeout waiting for response from host" );
			close( sock );
			return SAAS_OP_TIMEOUT_ERROR;
		}
	}
	else if ( ret <= 0 )
	{
		// we got some other error. uncool.
		logm( inst , LOG_WARNING , "error on socket: (%d) %s" , errno , strerror( errno ) );
		close( sock );
		return SAAS_OP_SOCKET_ERROR;
	}

	// awesome. we got data.
	*pkt_len = ret;
	close( sock );
	return SAAS_OP_SUCCESS;
}


uint16_t is_ipv4( const char *ip )
{
	struct sockaddr_in sock;
	if ( inet_pton( AF_INET , ip , &sock) == 1 )
		return SAAS_OP_SUCCESS;
	return SAAS_OP_ARG_ERROR;
}

uint16_t is_ipv6( const char *ip )
{
	struct sockaddr_in6 sock;
	if ( inet_pton( AF_INET6 , ip , &sock ) == 1 )
		return SAAS_OP_SUCCESS;
	return SAAS_OP_ARG_ERROR;
}


uint16_t copy_ipv4_addr( uint8_t *addr ,  const char *ip )
{
	struct in_addr sock;
	int ret = inet_pton( AF_INET , ip , &sock );
	if ( ret == 1 )
	{
		memcpy( addr , &sock , 4 );
		return SAAS_OP_SUCCESS;
	}
	return SAAS_OP_ARG_ERROR;
}

uint16_t copy_ipv6_addr( uint8_t *addr , const char *ip )
{
	struct in6_addr sock;
	int ret = inet_pton( AF_INET6 , ip , &sock );
	if ( ret == 1 )
	{
		memcpy( addr , &sock , 16 );
		return SAAS_OP_SUCCESS;
	}
	return SAAS_OP_ARG_ERROR;
}




